package com.github.lee.mvvm.core.vm

import androidx.annotation.DrawableRes
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.lee.mvvm.core.vm.exceptions.ResponseThrowable
import com.github.lee.mvvm.core.vm.exceptions.resultIsNull
import com.github.lee.mvvm.core.vm.exceptions.systemError
import com.github.lee.mvvm.core.vm.exceptions.unknownError
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext


open class BaseViewModel : ViewModel(), LifecycleCallback {
    val uiObserve = MutableLiveData<UiModel>()

    val throwableLiveData = MutableLiveData<ResponseThrowable>()


    enum class PageEvent {
        LOADING,
        SUCCESS,
        EMPTY,
        ERROR,
        WAIT,
        TOAST
    }

    data class UiModel(
        val pageEvent: PageEvent,
        @DrawableRes val icon: Int = -1,
        val msg: String = ""
    ) {
        companion object {
            fun loading(): UiModel =
                UiModel(PageEvent.LOADING)

            fun success(): UiModel =
                UiModel(PageEvent.SUCCESS)

            fun empty(@DrawableRes icon: Int = -1, msg: String = ""): UiModel =
                UiModel(PageEvent.EMPTY, icon, msg)

            fun error(@DrawableRes icon: Int = -1, msg: String = ""): UiModel =
                UiModel(PageEvent.ERROR, icon, msg)

            fun wait(): UiModel =
                UiModel(PageEvent.WAIT)

            fun toast(msg: String = ""): UiModel =
                UiModel(PageEvent.TOAST, -1, msg)
        }
    }

    //===Desc:=========================================================

    fun launchOnUI(block: suspend CoroutineScope.() -> Unit) {
        viewModelScope.launch { block() }
    }

    suspend fun <T> withContextIO(block: suspend CoroutineScope.() -> T) =
        withContext(Dispatchers.IO) { block }

    protected suspend fun <T> withContextMain(block: suspend CoroutineScope.() -> T) =
        withContext(Dispatchers.Main) { block }

    //===Desc:=========================================================

    fun postLoading() {
        uiObserve.postValue(UiModel.loading())
    }

    fun postSuccess() {
        uiObserve.postValue(UiModel.success())
    }

    fun postEmpty(@DrawableRes icon: Int = -1, msg: String = "") {
        uiObserve.postValue(UiModel.empty(icon, msg))
    }

    fun postError(@DrawableRes icon: Int = -1, msg: String = "") {
        uiObserve.postValue(UiModel.error(icon, msg))
    }

    fun postWait() {
        uiObserve.postValue(UiModel.wait())
    }

    fun postToast(msg: String) {
        uiObserve.postValue(UiModel.toast(msg))
    }

    //===Desc:=========================================================

    fun <T> requestForResult(
        block: suspend CoroutineScope.() -> T?,
        success: suspend CoroutineScope.(T) -> Unit
    ) {
        launchOnUI {
            try {
                val result = block()
                if (null == result) {
                    throw resultIsNull()
                } else {
                    success(result)
                }
            } catch (t: Throwable) {
                if (t is ResponseThrowable) {
                    throwableLiveData.postValue(t)
                } else {
                    val msg = t.message
                    if (msg.isNullOrEmpty()) {
                        throwableLiveData.postValue(unknownError())
                    } else {
                        throwableLiveData.postValue(systemError(msg))
                    }
                }
            }
        }
    }

}